2、Vue响应式的完善与完整过程的分析 您所在的位置:网站首页 vue 响应式 2、Vue响应式的完善与完整过程的分析

2、Vue响应式的完善与完整过程的分析

2023-04-13 19:01| 来源: 网络整理| 查看: 265

Vue响应式的完善与完整过程的分析

当前为Vue响应式的详细过程分析,并通过实现 cleanup与分支切换的方式去理解在响应式过程中发生的每一个步骤。到当前篇章为止,可熟练的掌握响应式的详细过程。后续内容为Vue api的实现

1、文件处理(可略过)

将第一篇文章中的两个方法单独放在两个不同的js文件中进行存放。

// !# reactive.js import { track, trigger } from "./effect.js"; // 响应式对象的创建 export function createReactiveObject(raw) {     // 创建一个proxy对象     const proxy = new Proxy(raw, {         // get数据获取的时候         get(target, key) {             const res = Reflect.get(target, key);             // TODO 依赖收集             track(target, key);             return res;         },         //  数据设置的时候         set(target, key, value) {             const res = Reflect.set(target, key, value);             // TODO 依赖触发             trigger(target, key);             return res;         }     });     // 返回一个响应式对象     return proxy; } 复制代码 // !# effect.js let activeEffect = ''; // const targetMap = new WeakMap(); // 副作用函数,用于执行 class ReactiveEffect {     constructor(fn) {         this._fn = fn;     }     run() {         activeEffect = this;         this._fn();     } } // targetMap: WeakMap  -> 内容为 target => depsMap // depsMap: Map  -> 内容为 key => deps // deps: Set -> 内容为 { effect...} export function track(target, key) {     if (!activeEffect) return;     // 从依赖收集Map中通过 target 提取 depsMap: Map - (key => deps)     let depsMap = targetMap.get(target);     if (!depsMap) {         // 如果depsMap不存在,表示首次收集,new Map 存入         depsMap = new Map();         targetMap.set(target, depsMap);     }     // 从 depsMap中通过key获取 deps: Set -  {  effect... }     let deps = depsMap.get(key);     if (!deps) {         // 如果 deps不存在,则给 depsMap添加一个new Set         deps = new Set();         depsMap.set(key, deps);     }     if (!deps.has(activeEffect)) {         // 如果deps中不存在activeEffect, 则对依赖进行收集         deps.add(activeEffect)     } } export function trigger(target, key) {     const depsMap = targetMap.get(target);     if (!depsMap) return;     let deps = depsMap.get(key);     deps?.forEach(effect => effect.run()); } export function effect(fn) {     let _effect = new ReactiveEffect(fn);     _effect.run(); } 复制代码 2、正文开始篇章

基于处理之后的函数,编写一个案例。 在案例中,通过手动的方式去修改 satus 和 text 的数据。

// !# index.html depsMap ——> deps的方式。去获取到deps,然后再遍历deps( deps由一个个 ReactiveEffect 组成),然后执行每一个 ReactiveEffect 的 run 方法。 run方法执行的时候,就会执行 render,然后render 的执行又会触发 track 的依赖进行重新收集。最后完成render 的执行。

3、track 与 trigger全流程

将trigger整合成流程图如下: 因为完整的trigger包含有 track 的流程:

track流程: run() ——> __fn执行 ——> render() ——> 读取代理对象 ——> 依赖收集进targetMap中 ——> render执行完毕(依赖收集完成后)

trigger流程: 修改代理对象内容 ——> 触发set ——> 从targetMap中获取依赖 ——> 遍历deps执行run ——> 触发track流程

trigger流程.jpg

targetMap的结构:

targetMap.png

depsMap的结构:

depsMap.jpg

deps是Set类型, 其数据结构就是 [ ReactiveEffect, ReactiveEffect, ... ]。

4、cleanup与分支切换

在targetMap中有两个依赖,status 和 text。 无论修改status 还是修改text。都会触发render进行渲染。 当status为 fasle的时候,无论text修改成什么内容。渲染的结果只会是hello。 但是由于targetMap中有 text的依赖。 所以当text修改的时候,还是会 触发render 的执行。有什么办法可以避免这种影响呢?

function render() { app.innerHTML = `${obj.status ? obj.text : 'hello'}` } 复制代码

在上面代码中, 当 status 为true时,会执行 text 的读取。所以track 会执行两次。 当 status 为false时,由于 text 并不会读取。所以track 只会执行一次。 假设,默认情况下 status 为true, 进行了两次的tack。再targetMap中,存在 status 的依赖以及text 的依赖。 然后在 主动设置 status 为 false 的时候,具体流程如下代码;

// 1、set set(target, key, value) { const res = Reflect.set(target, key, value); // TODO 依赖触发 trigger(target, key); return res; } // 2、trigger 中的依赖触发 function trigger(target, key) { .... ... deps?.forEach(effect => effect.run()); } // 3、run的执行会触发 ReactiveEffect 中的run run() { activeEffect = this;     this._fn(); // fn 执行 } // 4、 fn的执行会触发 render的执行 app.innerHTML = `${obj.status ? obj.text : 'hello'}` // 5、 render的执行会重新触发track。 但是由于status 为false。 所以 text 不会被读取,所以text不会track get(target, key) { const res = Reflect.get(target, key); // TODO 依赖收集 track(target, key); return res; }, 复制代码

也就是说 当 status 被手动更改为 fasle 的时候,会触发当前依赖的更新。 如果在run的时候( 上面3 ),把所有的依赖进行清空。 但是 4 和 5 仍然会继续进行。然后因为 三元表达式的问题。并不会触发 text 的读取。所以只会进行 status 的依赖收集。 最后由于 text 中的deps 不存在。所以在修改text 的时候 deps?.forEach(effect => effect.run()) 不会执行渲染。 从而完美的解决问题所在。

那么如何拿到deps,然后清空他呢?

1681215046096.jpg

分析上面完整的流程, 其中与deps 有关的,就是在红色框内圈出的内容。 track是把deps收集起来,trigger的时候获取 deps。 如果在track的时候,把 deps 指向一个全局存储的空间,那么在 run 的时候就可以使用起来。 全局中有activeEffect 变量。 所以稍微对 ReactiveEffect 和 track 进行改造 。

// track 方法 export function track(target, key) { ...... ...... if (!deps.has(activeEffect)) {         deps.add(activeEffect);         // 给全局 activeEffect 添加一个deps进行存储         activeEffect.deps.push(deps);     } } class ReactiveEffect {     constructor(fn) {         this._fn = fn;         this.deps = []; // 添加一个存储deps 的数组,因为 ReactiveEffect与activeEffect相同     }     run() {         cleanupEffect(this);         activeEffect = this;         this._fn();     } } // 清空deps的方法 function cleanupEffect(effect) { const { deps } = effect;     if (deps.length) {         deps.forEach(item => item.delete(effect))         deps.length = 0     } } 复制代码

由于trigger 中对deps 进行遍历 run 的时候,会去执行cleanupEffect 对deps进行delete,然后 fn执行时,又会对依赖进行add。 循环内进行数据删除后又添加,会导致无限循环。 想要避免无限递归的出现,那么在循环前,拷贝一份数据遍历,这样就可以避免对同一份数据进行修改。 重新修改trigger 方法。

export function trigger(target, key) {     const depsMap = targetMap.get(target);     if (!depsMap) return;     let deps = depsMap.get(key);     triggerEffect(new Set(deps)); } export function triggerEffect(deps) {     deps?.forEach(effect => effect.run()); } 复制代码

这样分支切换功能就完成实现了。

总结

在上面的文章中,解释了完整的响应式执行流程。并通过实现cleanup去解读 track与trigger的过程,在其中添加分支切换的功能,掌握响应式流程已经问题不大。

全部代码如下:

// !#effect.js let activeEffect = ''; // const targetMap = new WeakMap(); // 副作用函数,用于执行 class ReactiveEffect { constructor(fn) { this._fn = fn; this.deps = []; } run() { console.log("ReactiveEffect run ~"); cleanupEffect(this); activeEffect = this; this._fn(); } } function cleanupEffect(effect) { const { deps } = effect; if (deps.length) { console.log("cleanupEffect清空依赖"); deps.forEach(item => item.delete(effect)) deps.length = 0 } } // targetMap: WeakMap -> 内容为 target => depsMap // depsMap: Map -> 内容为 key => deps // deps: Set -> 内容为 { effect...} export function track(target, key) { if (!activeEffect) return; console.log("track~"); // 从依赖收集Map中通过 target 提取 depsMap: Map - (key => deps) let depsMap = targetMap.get(target); if (!depsMap) { // 如果depsMap不存在,表示首次收集,new Map 存入 depsMap = new Map(); targetMap.set(target, depsMap); } // 从 depsMap中通过key获取 deps: Set - { effect... } let deps = depsMap.get(key); if (!deps) { // 如果 deps不存在,则给 depsMap添加一个new Set deps = new Set(); depsMap.set(key, deps); } if (!deps.has(activeEffect)) { // 如果deps中不存在activeEffect, 则对依赖进行收集 deps.add(activeEffect); activeEffect.deps.push(deps); } } export function trigger(target, key) { console.log("trigger~"); const depsMap = targetMap.get(target); if (!depsMap) return; let deps = depsMap.get(key); triggerEffect(new Set(deps)); } export function triggerEffect(deps) { console.log("triggerEffect~"); deps?.forEach(effect => effect.run()); } export function effect(fn) { let _effect = new ReactiveEffect(fn); _effect.run(); } 复制代码 // !#reactive.js import { track, trigger } from "./effect.js"; // 响应式对象的创建 export function createReactiveObject(raw) { // 创建一个proxy对象 const proxy = new Proxy(raw, { // get数据获取的时候 get(target, key) { console.log("get的触发"); const res = Reflect.get(target, key); // TODO track(target, key); return res; }, // 数据设置的时候 set(target, key, value) { const res = Reflect.set(target, key, value); console.log("set的触发"); // TODO trigger(target, key); // effect(render); return res; } }); // 返回一个响应式对象 return proxy; } 复制代码 Document status text name import { effect } from "./src/effect.js"; import { createReactiveObject } from "./src/reactive.js"; const app = document.querySelector("#app"); let obj = createReactiveObject({ text: "effect3", status: true, }); // render 需要渲染的内容 function render() { app.innerHTML = `${obj.status ? obj.text : "hello"}`; renderName(); } effect(render); window.onChangeStatus = function () { obj.status = false } window.onChangeText = function () { obj.text = "1" } 复制代码


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有